package cn.coder.toolkit;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public final class DigestKit {

    private static final int DEFAULT_BUFFER_SIZE = 8192;
    public static final String MD2 = "MD2";
    public static final String MD5 = "MD5";
    public static final String SHA_1 = "SHA-1";
    public static final String SHA_224 = "SHA-224";
    public static final String SHA_256 = "SHA-256";
    public static final String SHA_384 = "SHA-384";
    public static final String SHA_512 = "SHA-512";
    public static final String SHA_512_224 = "SHA-512/224";
    public static final String SHA_512_256 = "SHA-512/256";
    public static final String SHA3_224 = "SHA3-224";
    public static final String SHA3_256 = "SHA3-256";
    public static final String SHA3_384 = "SHA3-384";
    public static final String SHA3_512 = "SHA3-512";

    private DigestKit() {}

    private static class Hex {

        /**
         * bytes 转 hex 字符串
         */
        public static String encode(byte[] bytes) {
            StringBuilder sb = new StringBuilder();
            for (byte b : bytes) {
                String hexStr = Integer.toHexString(b & 0xFF);
                if (hexStr.length() == 1) {
                    sb.append('0');
                }
                sb.append(hexStr);
            }
            return sb.toString();
        }

    }

    public static MessageDigest instance(String algorithm) {
        try {
            return MessageDigest.getInstance(algorithm);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    public static byte[] digest(String algorithm, byte[] data) {
        MessageDigest instance = instance(algorithm);
        instance.update(data);
        return instance.digest();
    }

    public static byte[] digest(String algorithm, ByteBuffer data) {
        MessageDigest instance = instance(algorithm);
        instance.update(data);
        return instance.digest();
    }


    public static byte[] digest(String algorithm, InputStream stream) throws IOException {
        MessageDigest instance = instance(algorithm);
        byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
        int read = stream.read(buffer, 0, DEFAULT_BUFFER_SIZE);
        while (read > -1) {
            instance.update(buffer, 0, read);
            read = stream.read(buffer, 0, DEFAULT_BUFFER_SIZE);
        }
        return instance.digest();
    }

    public static byte[] digest(String algorithm, File file) throws IOException {
        try (final BufferedInputStream inputStream = new BufferedInputStream(Files.newInputStream(file.toPath()))) {
            return digest(algorithm, inputStream);
        }
    }

    public static byte[] digest(String algorithm, FileChannel channel) throws IOException {
        MessageDigest instance = instance(algorithm);
        ByteBuffer buffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
        while (channel.read(buffer) > 0) {
            buffer.flip();
            instance.update(buffer);
            buffer.clear();
        }
        return instance.digest();
    }

    public static byte[] digest(String algorithm, Path path, OpenOption... options) throws IOException {
        try (final BufferedInputStream inputStream = new BufferedInputStream(Files.newInputStream(path, options))) {
            return digest(algorithm, inputStream);
        }
    }

    public static byte[] digest(String algorithm, RandomAccessFile data) throws IOException {
        return digest(algorithm, data.getChannel());
    }

    public static byte[] digest(String algorithm, String data) {
        return digest(algorithm, data.getBytes(StandardCharsets.UTF_8));
    }

    public static String md5(String string) {
        return Hex.encode(digest(MD5, string));
    }

    public static String md5(InputStream inputStream) throws IOException {
        return Hex.encode(digest(MD5, inputStream));
    }

    public static String md5(Path path) throws IOException {
        return Hex.encode(digest(MD5, path));
    }

    public static String md5(File file) throws IOException {
        return Hex.encode(digest(MD5, file));
    }

}
